package com.citywithincity.test.utils;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.util.ByteArrayBuffer;

import com.citywithincity.crypt.AesDataCrypt;
import com.citywithincity.utils.IoUtil;
import com.citywithincity.utils.JsonUtil;
import com.citywithincity.utils.MD5Util;
import com.citywithincity.utils.MapUtil;
import com.damai.annotation.Crypt;
import com.damai.error.ApiError;

/**
 * 非线程安全
 * @author renxueliang
 *
 */
public class ApiPost {
	//public static String BASE_URL = "http://110.80.22.108:28080/api/";
	public static String BASE_URL = "http://127.0.0.1:8091/api/";
	public static final ApiPost get(){
		return new ApiPost(BASE_URL, "18659210057",MD5Util.md5Appkey("123456"));
	}
	
	private static Log log = LogFactory.getLog(ApiPost.class);
	/*public static final String BASE_URL = "http://192.168.1.244:8080/api/";
	public static final String ACCOUNT = "18006019561";
	public static final String PWD = "123456";
	*/
	
	private String baseUrl;
	private String account;
	private String pwd;
	
	private String token;

	private AesDataCrypt aes = new AesDataCrypt();
	private String key;
	private String uploadKey;
	private int time;
	private String deviceID;
	
	public ApiPost(String baseUrl,String account){
		this(baseUrl, account, MD5Util.md5Appkey("123456"));
	}
	public ApiPost(String baseUrl,String account,String pwd){
		this.baseUrl = baseUrl;
		this.account =account;
		this.pwd =pwd;
		this.deviceID = MD5Util.md5Appkey(account);
	}

	@SuppressWarnings("unchecked")
	public void requestToken() throws IOException {
		
		Map<String, Object> json = new HashMap<String, Object>();
		Map<String, Object> content = rawPost("gate/token",json,Crypt.DOWNLOAD);
		int flag = (Integer) content.get("flag");
		if(flag==0){
			content = (Map<String, Object>) content.get("result");
			key = (String) content.get("key");
			token = (String) content.get("token");
			int start = 16 - time;
			uploadKey = key.substring(start, start + 16);
		}else{
			throw new ApiError("获取token失败");
		}
		
	}
	// 数据签名
	
	public boolean login() throws IOException{
		Map<String, Object> json = new HashMap<String, Object>();
		json.put("account", account);
		json.put("pwd", pwd);
		json.put("platform", "android");
		Object data = post("gate/login", json,Crypt.BOTH);
		if(data !=null){
			//成功
			return true;
		}
		throw new ApiError("登录失败");
	}
	

	@SuppressWarnings("unchecked")
	public Map<String, Object> getObject(String content) throws IOException {
		Map<String, Object> map = JsonUtil.read(content);
		return (Map<String, Object>) map.get("result");
	}
	
	private static final Map<String, String> header = Collections.emptyMap();
	public Map<String, Object> request(String api,Map<String, Object> data) throws IOException{
		
		String result = ApiHttpUtil.rawPost(baseUrl + api, header, MapUtil.toQueryString(data));
		return JsonUtil.read(result);
	}
	
	
	public ApiResult post(String api) throws IOException{
		return post(api, null,Crypt.NONE);
	}
	public ApiResult post(String api, Map<String, Object> jsonObject) throws IOException{
		return post(api, jsonObject,Crypt.NONE);
	}
	
	private PostData getPostData(Map<String, Object> jsonObject,int crypt) throws IOException{
		Map<String, String> headers = new HashMap<String, String>();
		headers.put("token", token);
		Map<String, Object> data = getInitObject(jsonObject);
		
		data.remove("sign");
		if(key!=null){
			data.put("sign", sign( data,key ));
		}else {
			data.put("sign", sign( data,deviceID ));
		}
		String post = JsonUtil.toJson(data);
		byte[] postData = null;
		
		if( (crypt & Crypt.UPLOAD ) >0){
			//增加header
			try {
				postData = aes.encript(post.getBytes("UTF-8"), uploadKey.getBytes());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}else{
			postData = post.getBytes("UTF-8");
		}
		
		// 这里
		Map<String, Object> files = getFiles();
		if (files.size() > 0) {
			try {
				postData = handleFiles(postData, files, headers);
			} catch (IOException e) {
				throw new IOException("不能加载文件，请重新选择");
			}
		}
		headers.put("totalLength", String.valueOf(postData.length));
		
		return new PostData(headers,postData);
	}
	
	private static class PostData{
		Map<String, String> headers;
		byte[] postDatao;
		
		public PostData(Map<String, String> headers,byte[] postData){
			this.headers = headers;
			this.postDatao = postData;
		}
	}
	
	private byte[] decrypt(byte[] buffer){
		String aesKey = null;
		if(key!=null){
			//解密
			aesKey = key.substring(time, time + 16);
		}else{
			aesKey = deviceID.substring(time,time + 16);
		}
		try {
			return aes.decript(buffer, aesKey.getBytes());
		} catch (Exception e) {
			throw new ApiError(String.format("[%s]数据解密失败,pinKey:%s", getAccount(),aesKey));
		}
	}
	
	private Map<String, Object> getResult(HttpURLConnection connection,int crypt) throws IOException{
		Map<String, Object> content = null;
		if(crypt>=Crypt.DOWNLOAD){
			int len = connection.getContentLength();
			byte[] buffer = new byte[len];
			connection.getInputStream().read(buffer);
			do{
				if(buffer[0]=='{' && buffer[buffer.length-1]=='}'){
					try{
						content = JsonUtil.read(buffer);
						break;
					}catch(IOException e){
						
					}
				}
				content = JsonUtil.read(decrypt(buffer));
				break;
			}while(true);
		}else{
			content= JsonUtil.read(connection.getInputStream());
		}
		log.info(getAccount() + ":" + content);
		return content;
	}
	public static HttpURLConnection createConnection(String url,Map<String, String> headers,byte[] content) throws IOException{
		URL postUrl = new URL(url);
		HttpURLConnection connection = (HttpURLConnection) postUrl.openConnection();
		connection.setDoOutput(true);
		connection.setDoInput(true);
		connection.setReadTimeout(10000);
		connection.setConnectTimeout(10000);
		connection.setRequestMethod("POST");
		if(headers!=null){
			for (Entry<String, String> entry : headers.entrySet()) {
				connection.setRequestProperty(entry.getKey(), entry.getValue());
			}
			
		}
		connection.setUseCaches(false);
		connection.setInstanceFollowRedirects(true);
		connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
		connection.setRequestProperty("Content-Length", String.valueOf(content.length));
	//	connection.setChunkedStreamingMode(5);
		return connection;
	}
	public static HttpURLConnection connect(String url, Map<String, String> headers,byte[] content) throws IOException {
		HttpURLConnection connection = createConnection(url,headers,content);
		DataOutputStream out = null;
		try{
			//write
			if(content!=null){
				out = new DataOutputStream(connection.getOutputStream());
				out.write(content);
				out.flush();
			}
		}finally{
			if(out!=null)try{out.close();}catch (Exception e) {}
		}
		return connection;
	}
	private Map<String, Object> rawPost(String api,Map<String, Object> jsonObject ,int crypt) throws IOException{

		log.info(getAccount() + ": Start post " + baseUrl + api + ":" + jsonObject);
		PostData postData = getPostData(jsonObject, crypt);
		
		HttpURLConnection connection = connect(baseUrl + api,postData.headers, postData.postDatao);
		
		return getResult(connection, crypt);
	}
	
	
	public ApiResult post(String api, Map<String, Object> jsonObject,int crypt) throws IOException {
		if(token==null){
			requestToken();
		}
		Map<String, Object> content = rawPost(api, jsonObject, crypt);
		int flag = (Integer) content.get("flag");
		if(flag==0){
			return new ApiResult(content);
		}else if(flag==7){
			//登录,并再次提交
			login();
			return post(api, jsonObject, crypt);
		}else if(flag==9){
			log.info("token无效"+getAccount()+"获取token...");
			requestToken();
			return post(api, jsonObject, crypt);
		}if(flag==-1){
			//alett
			throw new ApiError( getAccount() +":"+ (String) content.get("result"));
		}else{
			throw new RuntimeException(getAccount() +":"+"其他服务器错误:" + flag);
		}
	}
	private byte[] handleFiles(byte[] src, Map<String, Object> files,
			Map<String, String> headers) throws IOException {
		
		if(files.size()==1){
			//可能为第一种
			for (String key : files.keySet()) {
				if(key.isEmpty()){
					return handleFiles(src, (File)files.get(key), headers);
				}
			}
		}
		
		
		StringBuilder sb = new StringBuilder();
		boolean first = true;

		int fileLength = 0;
		for (Entry<String, Object> entry : files.entrySet()) {
			Object obj = entry.getValue();
			if (obj instanceof File) {
				File file = (File) obj;
				fileLength += file.length();
			}else if(obj instanceof byte[]){
				fileLength += ((byte[])obj).length;
			}
		}
		byte[] bytes = new byte[src.length + fileLength];
		System.arraycopy(src, 0, bytes, 0, src.length);
		int start = src.length;
		for (Entry<String, Object> entry : files.entrySet()) {
			if (first) {
				first = false;
			} else {
				sb.append(',');
			}

			Object obj = entry.getValue();
			int len = 0;
			if (obj instanceof File) {
				File file = (File) obj;
				FileInputStream reader = null;
				len = (int) file.length();
				try {
					reader = new FileInputStream(file);
					reader.read(bytes, start, len);
				} finally {
					IoUtil.close(reader);
				}

			} else if (obj instanceof byte[]) {
				byte[] file = (byte[]) obj;
				len = file.length;
				System.arraycopy(file, 0, bytes, start, len);
			}
			sb.append(entry.getKey());
			sb.append(':');
			sb.append(len);
			start += len;
		}

		headers.put("attach", sb.toString());
		headers.put("fileLength", String.valueOf(fileLength));
		return bytes;
	}

	
	
	private byte[] handleFiles(byte[] src, File file, Map<String, String> headers) throws IOException {
		ByteArrayBuffer buffer = new ByteArrayBuffer(src.length);
		buffer.append(src,0,src.length);
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		byte[] content = IoUtil.readFromFile(file);
		buffer.append(content,0,content.length);
		if(first){
			first = false;
		}else{
			sb.append(',');
		}
		sb.append(file.length());
		headers.put("attach", sb.toString());
		headers.put("Content-Length", String.valueOf(buffer.length()));
		return buffer.toByteArray();
	}

	public Map<String, Object> getInitObject(){
		return getInitObject(null);
	}
	
	public Map<String, Object> getInitObject(Map<String, Object> jsonObject){
		if(jsonObject==null){
			jsonObject = new HashMap<String, Object>();
		}
		jsonObject.put("deviceID", deviceID);
		//jsonObject.put("token", token);
		time = (int) (System.currentTimeMillis()/1000);
		jsonObject.put("time",time);
		jsonObject.put("rand_str",MD5Util.md5Appkey(String.valueOf(System.currentTimeMillis())));
		time =16- time % 16;
		return jsonObject;
	}

	
	
	
	private Map<String, Object> files = new LinkedHashMap<String, Object>();

	public String sign(Map<String, Object> json,String key){
		files.clear();
		StringBuilder sb = new StringBuilder();
		toMap(json, sb,null);
		sb.append(key);
		return MD5Util.md5Appkey(sb.toString());
	}

	public Map<String, Object> getFiles() {
		return files;
	}
	
	private void toMap(Map<String, Object> json,StringBuilder sb,String path){
		//这里开始签名
    	Map<String,Object> map = new TreeMap<String,Object>();
    	for (Entry<String, Object> entry : json.entrySet()) {
			Object value = entry.getValue();
			String key = entry.getKey();
			if(value instanceof File){
				files.put(getPath(path, key), value);
				map.put(key, null);
				continue;
			}else if(value instanceof byte[]){
				files.put(getPath(path, key), value);
				map.put(key, null);
				continue;
			}
			map.put(key, value);
		}
    	mapToString(map, sb,path);
	}
	
	@SuppressWarnings("unchecked")
	private void mapToString(Map<String,Object> map ,StringBuilder sb,String path){
		for (Entry<String, Object> entry : map.entrySet()) {
			Object value = entry.getValue();
			if(value instanceof List){
				String key =entry.getKey();
				sb.append(key);
				toList((List<Object>)value,sb,getPath(path,key));
			}else{
				sb.append(entry.getKey()).append(value);
			}
		}
	}
	
	private static String getPath(String path,String key){
		if(path==null){
			return key;
		}
		return path+"."+key;
	}
	
	private static String getPath(String path,int index){
		if(path==null){
			return "["+index+"]";
		}
		return path+"["+index+"]";
	}
	
	@SuppressWarnings("unchecked")
	private void toList(List<Object> json,StringBuilder sb,String path){
		for(int i=0, count = json.size(); i < count; ++i){
			Object value = json.get(i);
			if(value instanceof Map){
				toMap((Map<String, Object>)value, sb,getPath(path,i));
			}else {
				sb.append(value);
			}
		}
	}

	public String getAccount() {
		return account;
	}

	public void setAccount(String account) {
		this.account = account;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	
	
	
}
